import 'dart:io';

import 'package:dio/dio.dart';
import 'package:flutter/material.dart';
import 'package:xbaistack/l10n/status_code_mapping.dart';
import 'package:xbaistack/model/result.dart';
import 'package:xbaistack/utils/helper.dart';
import 'package:xbaistack/widget/network_listener.dart';
import 'package:xbaistack/widget/state_view.dart';

typedef DataFuture<DATA> = Future<DATA> Function();
typedef DataBuilder<DATA> = Widget Function(DATA);

/// 通用数据加载器，你需要提供一个异步任务 [Future]，组件负责执行此任务，并且在任务的各个状态展示合适的占位视图提醒。
/// 具体功能点：
/// 1. 自动检测网络状态，在首次运行检测无网络的情况下，展示无网络提示，并提供重试操作；
/// 2. 提供在执行异步任务过程中的状态视图展示；
///
/// * @author xbaistack
/// * @since 2025/04/01
class DataLoader<DATA> extends StatefulWidget {
  final DataFuture<DATA> future;
  final DataBuilder<DATA> builder;

  const DataLoader({super.key, required this.future, required this.builder});

  @override
  State<StatefulWidget> createState() => _DataLoaderState<DATA>();
}

class _DataLoaderState<DATA> extends State<DataLoader<DATA>> {
  DATA? _data;
  Future<DATA>? _future;

  @override
  void dispose() {
    super.dispose();
    _data = null;
    _future = null;
  }

  @override
  Widget build(BuildContext context) {
    // 防止重复加载
    if (_data != null) {
      return widget.builder.call(_data as DATA);
    }
    // 仅首次以及发起重试时会执行这部分代码
    return NetWorkListener(
      builder: (context) => FutureBuilder<DATA>(
        key: GlobalKey(), // 一定要设置，否则有缓存不会重新加载，重试就没效果了。
        future: _future ??= widget.future.call(),
        builder: (context, snapshot) {
          final state = snapshot.connectionState;

          // 加载中
          if (state == ConnectionState.active ||
              state == ConnectionState.waiting) {
            return StatusView();
          }

          // 异步任务发生错误
          if (snapshot.hasError) {
            final error = snapshot.error; // dio / result
            final result = error is Result ? error : _wrapException(error!);
            return _resultError(result);
          }

          // 请求成功且有数据
          if (state != ConnectionState.none && snapshot.hasData) {
            _data = snapshot.data;
            if (Helper.isNotEmpty(_data)) {
              return widget.builder.call(_data as DATA);
            }
          }

          // 控数据
          return StatusView(status: Status.EMPTY, retry: _retry);
        },
      ),
    );
  }

  /// 用于发起重试的处理函数
  void _retry() {
    setState(() {
      _data = null;
      _future = null;
    });
  }

  /// 将 [Result] 错误结果通过 [StatusView] 不同状态视图展示出来
  ///
  /// * @param [error] 错误结果
  /// * @return [Widget] 处理过后的错误占位视图
  Widget _resultError(Result error) {
    switch (error.code) {
      case StatusCode.SEND_TIMEOUT:
      case StatusCode.RECEIVE_TIMEOUT:
      case StatusCode.CONNECTION_TIMEOUT:
        return StatusView(
          status: Status.TIMEOUT,
          message: error.message,
          retry: _retry,
        );
      case StatusCode.NETWORK_ERROR:
        return StatusView(
          status: Status.NETWORK_ERROR,
          message: error.message,
          retry: _retry,
        );
      default:
        return StatusView(
          status: Status.FAILED,
          message: error.message,
          retry: _retry,
        );
    }
  }

  /// 错误异常归一化，统一的将错误转换为 [Result] 类型，便于我们集中处理错误展示。
  ///
  /// * @param [err] 错误对象，此对象可能是有未受掌控的 [dio] 发出的，也有可能是其他的错误。
  /// * @return [Result] 归一化的错误结果，后续我们通过状态码区分具体的错误类型。
  Result _wrapException(Object err) {
    // 除了网络请求之外的其他错误异常，统统归到未知异常中去。
    if (err! is DioException) {
      return Result.of(StatusCode.UNKNOWN, message: err.toString());
    }
    // 这部分是由未受到管控的 [dio] 发送网络请求导致的错误异常
    final error = err as DioException;
    switch (error.type) {
      case DioExceptionType.unknown:
        return _unknown(error);
      case DioExceptionType.connectionError:
        return Result.of(StatusCode.NETWORK_ERROR);
      case DioExceptionType.sendTimeout:
        return Result.of(StatusCode.SEND_TIMEOUT);
      case DioExceptionType.connectionTimeout:
        return Result.of(StatusCode.CONNECTION_TIMEOUT);
      case DioExceptionType.receiveTimeout:
        return Result.of(StatusCode.RECEIVE_TIMEOUT);
      case DioExceptionType.badCertificate:
        return Result.of(StatusCode.BAD_CERTIFICATE);
      case DioExceptionType.badResponse:
        return Result.of(err.response?.statusCode ?? 0);
      case DioExceptionType.cancel:
        return Result.of(StatusCode.CANCEL_REQUEST);
    }
  }

  /// 处理未知异常
  ///
  /// 目前已知异常类型：
  /// - HandshakeException：三次握手异常，通常由于 DNS 域名错误造成的无法请求；
  Result _unknown(DioException err) {
    Object? error = err.error;
    if (error is HandshakeException) {
      return Result.of(StatusCode.DOMAIN_ERROR);
    }
    return Result.of(StatusCode.UNKNOWN);
  }
}
